#region License

// This file is part of Grawlix.
// 
// Grawlix is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// Grawlix is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License along with Grawlix.  If not, see
// <http://www.gnu.org/licenses/>.

#endregion

using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.Serialization;
using System.Security.Permissions;
using C5;
using Castle.Core;
using Castle.MicroKernel;
using Castle.Windsor;
using Castle.Windsor.Installer;
using Grawlix.Concurrency;
using Grawlix.Services.Rendering;
using Rhino.Commons.Binsor;
using Thread=System.Threading.Thread;

namespace Grawlix.Services
{
	/// <summary>
	/// The root service provider of the game.
	/// </summary>
	[SuppressMessage("Microsoft.Usage", "CA2229",
		Justification = "GameKernel recreates itself by proxy.")]
	[Serializable]
	public sealed class GameKernel :
		PermanentMarshalByRefObject, IGameKernel, ISerializable
	{
		/// <summary>
		/// The asynchronously loading future operation.
		/// </summary>
		private readonly Future _asyncLoad;

		/// <summary>
		/// The internal implementation.
		/// </summary>
		private readonly IWindsorContainer _windsor;

		/// <summary>
		/// Initializes this <see cref="GameKernel"/> by wrapping an 
		/// <see cref="IWindsorContainer"/>.
		/// </summary>
		/// <param name="name">A name for the container.</param>
		/// <param name="configFile">The configuration for the 
		/// <see cref="WindsorContainer"/>.</param>
		/// <param name="parentKernel">The parent kernel to use.</param>
		/// <exception cref="ArgumentNullException"><c>name</c> is
		/// null.</exception>
		/// <exception cref="ArgumentNullException"><c>configFile</c>
		/// is null.</exception>
		public GameKernel(string name, string configFile,
		                  IWindsorContainer parentKernel)
		{
			if (name == null)
				throw new ArgumentNullException("name");
			if (configFile == null)
				throw new ArgumentNullException("configFile");
			_windsor = new WindsorContainer(name, new DefaultKernel(),
			                                new DefaultComponentInstaller
			                                	());
			// HACK: Big hack here.
			_windsor.Kernel.AddComponentInstance(
				"parent.render.device", typeof(IRenderDevice),
				parentKernel.Resolve<IRenderDevice>());
			_windsor.Kernel.AddComponentInstance(
				"parent.game.loader", typeof(IGameLoader),
				parentKernel.Resolve<IGameLoader>());
			_asyncLoad = Future.Fork(() =>
				{
					BooReader.Read(_windsor, configFile);
					Thread.MemoryBarrier();
				});
		}

		/// <summary>
		/// Gets the list of all the <see cref="Domain"/> objects.
		/// </summary>
		/// <value>
		/// An immutable list of all the loaded <see cref="Domain"/>
		/// objects.
		/// </value>
		public IList<Domain> Domains
		{
			get { return _windsor.Resolve<IDomainProvider>().Domains; }
		}

		/// <summary>
		/// Gets any failure that occurred during game load.
		/// </summary>
		/// <value>The failure that occurred during loading, or null if
		/// there is no failure.</value>
		public Exception Failure
		{
			get
			{
				Thread.MemoryBarrier();
				if (_asyncLoad != null && _asyncLoad.Failure != null)
					return _asyncLoad.Failure;
				return Main.Failure;
			}
		}

		/// <summary>
		/// Gets the main controlling domain for this game.
		/// </summary>
		/// <value>The main controlling domain.</value>
		public Domain Main
		{
			get
			{
				Domain domain = _windsor.Resolve<IDomainProvider>().Main;
				domain.Activate(this);
				return domain;
			}
		}

		/// <summary>
		/// Gets the kernel name.
		/// </summary>
		/// <value>A name for the kernel.</value>
		public string Name
		{
			get { return _windsor.Name; }
		}

		/// <summary>
		/// Gets a value indicating whether this 
		/// <see cref="GameKernel"/> is finished loading.
		/// </summary>
		/// <value><c>true</c> if finished loading; otherwise, 
		/// <c>false</c>.
		/// </value>
		/// <remarks>The fact that it has finished loading could
		/// indicate a failure to load.</remarks>
		public bool Ready
		{
			get
			{
				Thread.MemoryBarrier();
				if (_asyncLoad != null && !_asyncLoad.IsFinished)
					return false;
				return Main.Ready;
			}
		}

		/// <summary>
		/// Gets the <see cref="IWindsorContainer"/> that locates
		/// services.
		/// </summary>
		/// <value>
		/// The <see cref="IWindsorContainer"/> used to locate the
		/// services.
		/// </value>
		/// <exception cref="InvalidOperationException">Unable to use a
		/// <see cref="GameKernel"/> until it is ready.</exception>
		// NOTE: Should be cached.
		public IWindsorContainer WindsorContainer
		{
			get { return DynamicProxy.CreatePermanent(_windsor); }
		}

		/// <summary>
		/// Populates a 
		/// <see cref="T:System.Runtime.Serialization.SerializationInfo"/>
		/// with the data needed to serialize the target object.
		/// </summary>
		/// <param name="info">The 
		/// <see cref="T:System.Runtime.Serialization.SerializationInfo"/>
		/// to populate with data.</param>
		/// <param name="context">The destination (see 
		/// <see cref="T:System.Runtime.Serialization.StreamingContext"/>
		/// ) for this serialization.</param>
		/// <exception cref="T:System.Security.SecurityException">
		/// The caller does not have the required permission.
		/// </exception>
		/// <exception cref="SerializationException">Unable to
		/// deserialize persistent data that relies on a game kernel
		/// without access to the game kernel.</exception>
		[SecurityPermission(SecurityAction.LinkDemand,
			Flags = SecurityPermissionFlag.SerializationFormatter)]
		public void GetObjectData(SerializationInfo info,
		                          StreamingContext context)
		{
			// Make sure kernel is present.
			GameKernelStreamingContext.GetContextKernel(context);
			info.SetType(typeof(GameKernelObjectReference));
		}

		/// <summary>
		/// Gets the service object of the specified type.
		/// </summary>
		/// <param name="serviceType">An object that specifies the type
		/// of service object to get.</param>
		/// <returns>
		/// A service object of type <paramref name="serviceType"/>.
		/// -or- null if there is no service object of type 
		/// <paramref name="serviceType"/>.
		/// </returns>
		[SuppressMessage("Microsoft.Design", "CA1033",
			Justification = "Already done, just not recognized.")]
		object IServiceProvider.GetService(Type serviceType)
		{
			return ResolveService(serviceType);
		}

		/// <summary>
		/// Gets the service.
		/// </summary>
		/// <typeparam name="T">The type of service to request.
		/// </typeparam>
		/// <returns>The requested service object.</returns>
		[SuppressMessage("Microsoft.Design", "CA1033",
			Justification = "Already done, just not recognized.")]
		[SuppressMessage("Microsoft.Design", "CA1004",
			Justification = "Follows interface declaration.")]
		T IServiceProviderEx.GetService<T>()
		{
			return ResolveService<T>();
		}

		/// <summary>
		/// Reloads the resource from disk.
		/// </summary>
		/// <param name="resource">The resource to reload.</param>
		public void ReloadResource<T>(T resource)
			where T : IDynamicResource
		{
			ResolveService<IDynamicResourceFactory<T>>().Reload(
				resource);
		}

		/// <summary>
		/// Finds or loads a domain with the specified name.
		/// </summary>
		/// <param name="name"></param>
		/// <returns>The domain, which may not yet be loaded.</returns>
		/// <exception cref="ArgumentNullException"><c>name</c> is
		/// null.</exception>
		public Domain ResolveDomain(string name)
		{
			if (name == null)
				throw new ArgumentNullException("name");

			Domain domain =
				_windsor.Resolve<IDomainProvider>().Resolve(name);
			domain.Activate(this);
			return domain;
		}

		/// <summary>
		/// Finds or creates a resource with the specified identifier.
		/// </summary>
		/// <typeparam name="T">The type of resource to resolve.
		/// </typeparam>
		/// <param name="identifier">The identifier of the resource.
		/// </param>
		/// <returns>The resolved resource, which may not be ready for
		/// use.</returns>
		[SuppressMessage("Microsoft.Design", "CA1004",
			Justification = "Follows interface declaration.")]
		public T ResolveResource<T>(Uri identifier)
			where T : IDynamicResource
		{
			return
				ResolveService<IDynamicResourceFactory<T>>().Resolve(
					identifier);
		}

		/// <summary>
		/// Finds a service of the given type.
		/// </summary>
		/// <typeparam name="T">The type of service to request.
		/// </typeparam>
		/// <returns>The requested service.</returns>
		[SuppressMessage("Microsoft.Design", "CA1004",
			Justification = "Follows interface declaration.")]
		public T ResolveService<T>()
		{
			return _windsor.Resolve<T>();
		}

		/// <summary>
		/// Finds a service of the given type, with the given key.
		/// </summary>
		/// <typeparam name="T">The type of service to request.
		/// </typeparam>
		/// <param name="key">The key of the service requested.</param>
		/// <returns>The requested service.</returns>
		[SuppressMessage("Microsoft.Design", "CA1004",
			Justification = "Follows interface declaration.")]
		public T ResolveService<T>(string key)
		{
			return _windsor.Resolve<T>(key);
		}

		/// <summary>
		/// Finds a service with the given key.
		/// </summary>
		/// <param name="key">The key of the service requested.</param>
		/// <returns>The requested service.</returns>
		public object ResolveService(string key)
		{
			return _windsor.Resolve(key);
		}

		/// <summary>
		/// Finds a service of the given type.
		/// </summary>
		/// <param name="service">The type of service requested.
		/// </param>
		/// <returns>The requested service.</returns>
		public object ResolveService(Type service)
		{
			return _windsor.Resolve(service);
		}

		/// <summary>
		/// Finds a service of the given type, with the given key.
		/// </summary>
		/// <param name="key">The key of the service requested.</param>
		/// <param name="service">The type of service requested.
		/// </param>
		/// <returns>The requested service.</returns>
		public object ResolveService(string key, Type service)
		{
			return _windsor.Resolve(key, service);
		}

		/// <summary>
		/// Returns a <see cref="T:System.String"/> that represents the
		/// current <see cref="T:System.Object"/>.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.String"/> that represents the current
		/// <see cref="T:System.Object"/>.
		/// </returns>
		public override string ToString()
		{
			return base.ToString() + "-" + Name;
		}
	}
}